#ifndef MATH_OPERATION_HPP
#define MATH_OPERATION_HPP

#include "tvector.hpp"
#include <algorithm>
#include <array>

/**
 * @brief  step function implementation
 * @note
 * @param  input: input value
 * @param  s: step value
 * @retval
 */
template <typename T>
T step( T input, T s ) {
  return static_cast<T>( input >= s );
}

template <typename T, ui8 col>
tvec<T, col> step( const tvec<T, col> &v, T s ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = static_cast<T>( v[i] >= s ); )
  return r;
}

/**
 * @brief  smoothstep function implementation
 * @note
 * @param  input: input value
 * @param  min: min value
 * @param  max: max value
 * @retval
 */
template <typename T>
T smoothstep( T input, T min, T max ) {
  // TODO: cubic smoothstep implementation
  if ( input < min ) return 0;
  if ( input >= max ) return 1;
  input = ( input - min ) / ( max - min );
  return input * input * ( 3 - 2 * input );
}

/**
 * @brief  reorder the vector through the index
 * @note
 * @param  v: permute vector
 * @param  index[col]: order used in x, y, z, etc
 * @retval
 */
template <typename T, ui8 col>
tvec<T, col> permute( const tvec<T, col> &v, ui8 index[col] ) {
  if constexpr ( col == 1 ) return v;
  tvec<T, col> r;
  LOOP_TVEC( r[i] = v[index[i]]; )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> abs( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::abs( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> floor( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::floor( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> ceil( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::ceil( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> pow( const tvec<T, col> &v, T s ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::pow( v[i], s ); )
  return r;
}

// log2
template <typename T, ui8 col>
tvec<T, col> log( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::log( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> log10( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::log10( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> exp( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::exp( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> sin( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::sin( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> cos( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::cos( v[i] ); )
  return r;
}

template <typename T, ui8 col>
tvec<T, col> tan( const tvec<T, col> &v ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = std::tan( v[i] ); )
  return r;
}

template <typename T>
T mod( T a, T b ) {
  i32 n = ( i32 )( a / b );
  a -= n * b;
  if ( a < 0 ) a += b;
  return a;
}

template <typename T, ui8 col>
tvec<T, col> mod( const tvec<T, col> &v, T m ) {
  tvec<T, col> r;
  LOOP_TVEC( r[i] = mod( v[i], m ); )
  return r;
}

#define Max( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) )
#define Min( x, y ) ( ( x ) > ( y ) ? ( y ) : ( x ) )

// TODO: may be use more simpler version?
template <typename T, ui64 size>
void normalizeValueInterval( const std::array<T, size> &value,
                             std::array<T, size> &output, T k ) {
  std::array<T, size> tmp;
  std::copy( value.begin(), value.end(), tmp.begin() );
  std::sort(tmp.begin(), tmp.end());
  T minVal = tmp.front();
  T maxVal = minVal;
  for (ui64 i = 0; i < size; i++) {
    maxVal = Max(maxVal, tmp.at(i) - minVal);
  }
  for (ui64 i = 0; i < size; i++) {
    output.at(i) = k * ((value.at(i) - minVal) / (maxVal + T(0.00001)));
  }
}

#endif